package com.example.seckill.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.to.mq.SecKillOrderTo;
import com.atguigu.common.utils.R;
import com.atguigu.common.vo.MemberRsepVo;
import com.example.seckill.interceptor.LoginUserInterceptor;
import com.example.seckill.service.SeckillService;
import com.example.seckill.feign.CouponFeignService;
import com.example.seckill.feign.ProductFeignService;
import com.example.seckill.to.SeckillSkuRedisTo;
import com.example.seckill.vo.SeckillSessionWithSkus;
import com.example.seckill.vo.SeckillSkuVo;
import com.example.seckill.vo.SkuInfoVo;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Service
@Slf4j
public class SeckillServiceImpl implements SeckillService {

    @Autowired
    CouponFeignService couponFeignService;

    @Autowired
    ProductFeignService productFeignService;

    @Autowired
    StringRedisTemplate redisTemplate;


    @Autowired
    RedissonClient redissonClient;

    @Autowired
    RabbitTemplate rabbitTemplate;

    private final static String SESSIONS_CACHE_PREFIX = "seckill:sessions:";
    private final static String SECKILL_CACHE_PREFIX = "seckill:skus";
    private final static String SKU_STOCK_SEMAPHORE = "seckill:stock:"; //+随机码

    @Override
    public void uploadSeckillSkuLatest3Days() {
        //1.扫描所有商品
        R r = couponFeignService.getLatest3DaysSessions();
        if (r.getCode() == 0) {
            List<SeckillSessionWithSkus> seckillSessionWithSkuses = r.getData(new TypeReference<List<SeckillSessionWithSkus>>() {
            });
            //2. 将session中商品放入到redis
            saveSessionInfo(seckillSessionWithSkuses);
            saveSessionSkuInfos(seckillSessionWithSkuses);
        }
    }

    @Override
    public List<SeckillSkuRedisTo> getCurrentSeckillSkus() {
        //1. 确定当前时间属于的秒杀场次
        long currentTime = System.currentTimeMillis();
        //得到所有场次开头的key
        Set<String> keys = redisTemplate.keys(SESSIONS_CACHE_PREFIX + "*");
        assert keys != null;
        for (String key: keys) {
            String s = key.replace(SESSIONS_CACHE_PREFIX, "");
            String[] strings = s.split("_");
            long start = Long.parseLong(strings[0]);
            long end = Long.parseLong(strings[1]);
            if (start <= currentTime && end >= currentTime) {
                List<String> range = redisTemplate.opsForList().range(key, 0, 100);
                BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(SECKILL_CACHE_PREFIX);
                assert range != null;
                List<String> results = hashOps.multiGet(range);
                assert results != null;
                return results.stream().map(res -> JSON.parseObject(res, new TypeReference<SeckillSkuRedisTo>() {
                })).collect(Collectors.toList());
            }
        }
        return null;
    }

    @Override
    public SeckillSkuRedisTo getSkuSeckillInfo(Long skuId) {
        //1. 找到参与秒杀的商品key
        BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(SECKILL_CACHE_PREFIX);
        Set<String> keys = hashOps.keys();
        if (keys != null) {
            String rex = "\\d_" + skuId;
            for (String key : keys) {
                if (Pattern.matches(rex, key)) {
                    String json = hashOps.get(key);
                    SeckillSkuRedisTo redisTo = JSON.parseObject(json, new TypeReference<SeckillSkuRedisTo>() {
                    });
                    long currentTime = System.currentTimeMillis();
                    assert redisTo != null;
                    if (currentTime < redisTo.getStartTime() || currentTime > redisTo.getEndTime()) {
                        redisTo.setRandomCode(null);
                    }
                    return redisTo;
                }
            }
        }
        return null;
    }

    @Override
    public String kill(String killId, String key, Integer num) {
        long s1 = System.currentTimeMillis();
        MemberRsepVo memberRsepVo = LoginUserInterceptor.threadLocal.get();
        //1. 获取当前商品的详细信息
        BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(SECKILL_CACHE_PREFIX);
        String json = hashOps.get(killId);
        if (json != null) {
            SeckillSkuRedisTo redisTo = JSON.parseObject(json, new TypeReference<SeckillSkuRedisTo>() {
            });
            //合法性校验
            long current = System.currentTimeMillis();
            if (redisTo.getEndTime() >= current && redisTo.getStartTime() <= current) {
                String redisId = redisTo.getPromotionSessionId() + "_" + redisTo.getSkuId();
                if (redisId.equals(killId)) {
                    BigDecimal limit = redisTo.getSeckillLimit();
                    if (num <= limit.intValue()) {
                        //用户若购买过，就在redis设置一个key，key：userId_sessionId_skuId
                        String userKey = memberRsepVo.getId() + "_" + redisTo.getPromotionSessionId() + "_" + redisTo.getSkuId();
                        long ttl = redisTo.getEndTime() - current; //秒杀过期就清除这些信息
                        if (Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(userKey, num.toString(), ttl, TimeUnit.MILLISECONDS))) {
                            RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + key);
                            try {
                                boolean b = semaphore.tryAcquire(num, 100, TimeUnit.MILLISECONDS);
                                if (b) {
                                    //验证成功，开始创建订单
                                    String orderSn = IdWorker.getTimeId();
                                    SecKillOrderTo secKillOrderTo = new SecKillOrderTo();
                                    secKillOrderTo.setOrderSn(orderSn);
                                    secKillOrderTo.setMemberId(memberRsepVo.getId());
                                    secKillOrderTo.setNum(num);
                                    secKillOrderTo.setPromotionSessionId(redisTo.getPromotionSessionId());
                                    secKillOrderTo.setSkuId(redisTo.getSkuId());
                                    secKillOrderTo.setSeckillPrice(redisTo.getSeckillPrice());
                                    rabbitTemplate.convertAndSend("order-event-exchange", "order.seckill.order",secKillOrderTo );
                                    long s2 = System.currentTimeMillis();
                                    log.info("秒杀验证+创建订单一共耗时：{}毫秒",(s2 - s1));
                                    return orderSn;
                                }
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                                return null;
                            }
                        }
                    }
                }

                }
            }
            return null;
        }



    private void saveSessionSkuInfos(List<SeckillSessionWithSkus> sessions) {
        BoundHashOperations<String, Object, Object> hashOps = redisTemplate.boundHashOps(SECKILL_CACHE_PREFIX);
        sessions.forEach(session -> {
            List<SeckillSkuVo> skus = session.getRelationSkus();
            for (SeckillSkuVo sku : skus) {
                String randomCode = UUID.randomUUID().toString().replace("-", "");
                if (Boolean.FALSE.equals(hashOps.hasKey(sku.getPromotionSessionId().toString() +"_"+ sku.getSkuId().toString()))) {
                    SeckillSkuRedisTo redisTo = new SeckillSkuRedisTo();
                    //sku基本信息
                    R r = productFeignService.getSkuInfo(sku.getSkuId());
                    if (r.getCode() == 0) {
                        SkuInfoVo skuInfoVo = r.getData("skuInfo",new TypeReference<SkuInfoVo>() {
                        });
                        redisTo.setSkuInfo(skuInfoVo);
                    }

                    //秒杀信息
                    BeanUtils.copyProperties(sku, redisTo);

                    redisTo.setStartTime(session.getStartTime().getTime());
                    redisTo.setEndTime(session.getEndTime().getTime());

                    redisTo.setRandomCode(randomCode);

                    hashOps.put(sku.getPromotionSessionId().toString() +"_"+ sku.getSkuId().toString(), JSON.toJSONString(redisTo));

                    if (Boolean.FALSE.equals(redisTemplate.hasKey(SKU_STOCK_SEMAPHORE + redisTo.getRandomCode()))) {
                        RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + redisTo.getRandomCode());
                        boolean b = semaphore.trySetPermits(sku.getSeckillCount().intValue());
                    }
                }


            }
        });
    }

    private void saveSessionInfo(List<SeckillSessionWithSkus> sessions) {
        sessions.forEach(session -> {
            long startTime = session.getStartTime().getTime();
            long endTime = session.getEndTime().getTime();
            String key = SESSIONS_CACHE_PREFIX + startTime + "_" + endTime;
            if (Boolean.FALSE.equals(redisTemplate.hasKey(key))) {
                redisTemplate.opsForList().leftPushAll(key, session.getRelationSkus().
                        stream().map(sku-> sku.getPromotionSessionId().toString() +"_"+ sku.getSkuId().toString()).collect(Collectors.toList()));
            }
        });
    }
}
